<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        li {
            list-style: none;
        }

        .wrap {
            margin-top: 20px;
        }

        #title {
            line-height: 60px;
            font-size: 32px;
            /* background: none;
            background-color: transparent; */
        }

        .list li {
            line-height: 40px;
            border: 1px dashed #333;
            text-indent: 10px;
        }

        .active {
            background-color: aquamarine;
        }
    </style>
</head>

<body>
    <div class="wrap">
        <h2 id="title">标题</h2>
        <ul class="list">
            <li>01</li>
            <li>02</li>
            <li>03</li>
            <li>04</li>
            <li>05</li>
            <li>06</li>
        </ul>
    </div>
    <p>
        <button class="setHtml">设置html</button>
        <button class="getHtml">获取html</button>
        <button class="hide">hide</button>
        <button class="show">show</button>
        <button class="setCss">设置css</button>
        <button class="getCss">获取css</button>
        <button class="setWidth">设置Width</button>
        <button class="getWidth">获取Width</button>
        <button class="attr">attr</button>
        <button class="prop">prop</button>
        <button class="addClass">addClass</button>
        <button class="removeClass">removeClass</button>
        <button class="hasClass">hasClass</button>
        <button class="eq">eq</button>
        <button class="siblings">siblings</button>
        <button class="chain">链式操作</button>
    </p>
</body>
<script>
    // DOM操作  =>  获取元素,操作元素(修改元素的内容 属性 样式 绑定事件),元素的增删改查

    // 目的: 对DOM操作进行封装,可以方便用户快捷获取元素,操作元素,以及元素的增删改查

    // 构造函数版  => 获取元素,返回获取的元素组成的集合(伪数组 -> 实例化对象)
    function GetEle(selector) { // selector 传入css选择器(字符串)   原生dom元素(单元素)  原生dom元素的集合(多元素) => 转化为对应的实例化对象

        if (typeof selector == "string") {
            // 传入的参数 css选择器(字符串) 
            var list = document.querySelectorAll(selector);  // 对应选择器获取元素 (伪数组)
            list = Array.from(list);  // 伪数组转真数组
        } else if (selector instanceof HTMLElement) {  // 传入的参数是html元素
            // 传入的参数  原生dom元素(单元素) => li 
            var list = [selector];  // 不管是单元素还是多元素统一放到集合中处理
        } else if (typeof selector == "object" && "length" in selector) {
            // 传入的参数是集合 (伪数组/真数组)   dom元素的集合(多元素) => [li,li,li,li]
            var list = Array.from(selector);
        } else {
            var err = new TypeError("请传入指定类型的参数");
            throw err;
        }


        Object.setPrototypeOf(list, GetEle.prototype); // 直接修改伪数组的[[Prototype]]可能会丢失length属性 => 先转真数组,真数组改伪数组

        // 自定义构造函数的返回值
        return list; // 返回伪数组, 有数组的结构,但是又可以访问GetEle.prototype上的方法
    }

    // 根据伪数组(实例化对象) => 操作集合中的元素

    // text()  获取和设置元素内的文本结构
    // text("123123")   有参数就设置
    // text()           没有参数就获取
    GetEle.prototype.text = function (con) { // 设置元素内文本结构
        // this -> 调用此方法的实例化对象 ((实例化对象可以继续访问GetEle.prototype上的方法 ))

        // arguments => 函数的内置参数,用于存本次函数调用时实际参数的集合

        if (arguments.length >= 1) {  // 有参数
            // 设置
            // for (var i = 0; i < this.length; i++) {
            //     var ele = this[i];
            //     ele.innerText = con;
            // }

            this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
                ele.innerText = con;
            })
            return $(this);  // 返回实例化对象(新的)

        } else { // 没有参数
            // 获取
            return this[0].innerText;
        }
    }

    // html()  获取和设置元素内的html结构
    // html("123123")   有参数就设置
    // html()           没有参数就获取
    GetEle.prototype.html = function (con) { // 设置元素内文本结构
        // this -> 调用此方法的实例化对象

        // arguments => 函数的内置参数,用于存本次函数调用时实际参数的集合

        if (arguments.length >= 1) {  // 有参数
            // 设置
            // for (var i = 0; i < this.length; i++) {
            //     var ele = this[i];
            //     ele.innerHTML = con;
            // }

            this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
                ele.innerHTML = con;
            })

            // return this;  // 返回实例化对象本身(原本的  => 伪数组)
            return $(this);  // 返回实例化对象(新的)

        } else { // 没有参数
            // 获取
            return this[0].innerHTML;
        }
    }


    // val()  获取和设置元素内的value值
    // val("123123")   有参数就设置
    // val()           没有参数就获取
    GetEle.prototype.val = function (con) { // 设置元素内文本结构
        // this -> 调用此方法的实例化对象

        // arguments => 函数的内置参数,用于存本次函数调用时实际参数的集合

        if (arguments.length >= 1) {  // 有参数
            // 设置
            // for (var i = 0; i < this.length; i++) {
            //     var ele = this[i];
            //     ele.value = con;
            // }

            this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
                ele.value = con;
            })

            return $(this);  // 返回实例化对象(新的)

        } else { // 没有参数
            // 获取
            return this[0].value;
        }
    }


    // hide()  隐藏元素
    // show()  显示元素

    GetEle.prototype.hide = function () {
        // this -> 调用此方法的实例化对象
        // for (var i = 0; i < this.length; i++) {
        //     var ele = this[i];
        //     ele.style.display = "none";
        // }

        this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
            ele.style.display = "none";
        })
        return $(this);  // 返回实例化对象(新的)
    }

    GetEle.prototype.show = function () {
        // this -> 调用此方法的实例化对象
        // for (var i = 0; i < this.length; i++) {
        //     var ele = this[i];
        //     ele.style.display = "block";
        // }
        this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
            ele.style.display = "block";
        })

        return $(this);  // 返回实例化对象(新的)
    }



    GetEle.prototype.on = function (type, callback) {
        // this -> 调用此方法的实例化对象
        // for (var i = 0; i < this.length; i++) {
        //     var ele = this[i];
        //     ele.addEventListener(type, callback);
        // }
        this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
            ele.addEventListener(type, callback);
        })
        return $(this);  // 返回实例化对象(新的)
    }

    // 对应事件类型  对on方法进行二次封装
    GetEle.prototype.click = function (callback) {
        // this -> 调用此方法的实例化对象 (实例化对象可以继续访问GetEle.prototype上的方法 )
        this.on("click", callback);
        return $(this);  // 返回实例化对象(新的)
    }

    GetEle.prototype.dblclick = function (callback) {
        // this -> 调用此方法的实例化对象 (实例化对象可以继续访问GetEle.prototype上的方法 )
        this.on("dblclick", callback);
        return $(this);  // 返回实例化对象(新的)
    }

    GetEle.prototype.mousedown = function (callback) {
        // this -> 调用此方法的实例化对象 (实例化对象可以继续访问GetEle.prototype上的方法 )
        this.on("mousedown", callback);
        return $(this);  // 返回实例化对象(新的)
    }

    // ....



    GetEle.prototype.off = function (type, callback) {
        // this -> 调用此方法的实例化对象
        // for (var i = 0; i < this.length; i++) {
        //     var ele = this[i];
        //     ele.removeEventListener(type, callback);
        // }
        this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
            ele.removeEventListener(type, callback);
        })
        return $(this);  // 返回实例化对象(新的)
    }

    GetEle.prototype.each = function (callback) {
        // this -> 调用此方法的实例化对象 (伪数组)
        for (var i = 0; i < this.length; i++) {
            callback(this[i], i, this);
        }
        return $(this);  // 返回实例化对象(新的)
    }

    // css()   获取和设置元素的css样式
    // css("backgrounc-color","red");  两个参数就是设置
    // css("backgrounc-color");       一个参数就是获取
    GetEle.prototype.css = function (cssKey, cssVal) {
        // this -> 调用此方法的实例化对象 (伪数组)
        if (arguments.length >= 2) { // 两个参数就是设置
            this.each((ele) => {
                ele.style[cssKey] = cssVal;
            })
            return $(this);  // 返回实例化对象(新的)
        } else if (arguments.length == 1) { // 一个参数
            // a. 如果参数是字符串 => css("backgrounc-color") 就是获取 (默认返回集合中的第一个)
            // b. 如果参数是对象类型 => {width:"200px",height:"200px"}  遍历对象(一个参数),取出键值对在设置

            if (typeof cssKey == "string") {
                // return this[0].style[cssKey];   // style只能获取行内样式 
                var ele = this[0];
                try {
                    return window.getComputedStyle(ele)[cssKey];
                } catch {
                    return ele.currentStyle[cssKey];
                }
            } else if (typeof cssKey == "object") {
                for (var key in cssKey) {
                    var val = cssKey[key];

                    // this.each((ele) => {
                    //     ele.style[key] = val;
                    // })
                    this.css(key, val);
                }
                return $(this);  // 返回实例化对象(新的)
            }


        } else {
            var err = new Error("Place enter at last one parameter!") // 请传入至少一个参数
            throw err;
        }
    }

    // width()   获取和设置元素的宽度
    // width("100px")   有一个参数就设置
    // width()          没有参数就获取(100px => 去单位 => 100)
    GetEle.prototype.width = function (val) {
        // this -> 调用此方法的实例化对象 (伪数组)

        // 取值时记得去单位  赋值时记得加单位
        if (arguments.length >= 1) {

            // 关于单位处理
            // (1) 如果有单位, 直接设置  => 100 px em rem pt % vmin vmax vw vh
            // (2) 如果没有单位, 默认px 
            var unitReg = /(px|em|rem|%|pt|vmin|vmax|vw|vh)$/;
            if (unitReg.test(val)) {
                this.css("width", val);
            } else {
                this.css("width", val + "px");
            }

            return $(this);  // 返回实例化对象(新的)
        } else {
            return parseInt(this.css("width"));
        }
    }

    GetEle.prototype.height = function (val) {
        // this -> 调用此方法的实例化对象 (伪数组)
        if (arguments.length >= 1) {

            // 关于单位处理
            // (1) 如果有单位, 直接设置  => 100 px em rem pt % vmin vmax vw vh
            // (2) 如果没有单位, 默认px 
            var unitReg = /(px|em|rem|%|pt|vmin|vmax|vw|vh)$/;
            if (unitReg.test(val)) {
                this.css("height", val);
            } else {
                this.css("height", val + "px");
            }

            return $(this);  // 返回实例化对象(新的)
        } else {
            return parseInt(this.css("height"));
        }
    }



    // attr()   获取和设置元素的html属性(标签内可见的 class id title data-xxx)
    // attr("class","one");  两个参数就是设置  等价于ele.setAttribute("class","one")
    // attr("class");       一个参数就是获取  等价于ele.getAttribute("class","one")
    GetEle.prototype.attr = function (key, val) {
        // this -> 调用此方法的实例化对象 (伪数组)

        if (arguments.length >= 2) {
            this.each((ele, index) => {
                ele.setAttribute(key, val);
            })
            return $(this);  // 返回实例化对象(新的)
        } else if (arguments.length == 1) {
            return this[0].getAttribute(key);
        } else {
            var err = new Error("Place enter at last one parameter!") // 请传入至少一个参数
            throw err;
        }
    }


    // prop()   获取和设置元素节点的内置属性(className id title dataset  clientWidth offsetLeft )
    // prop("className","one");  两个参数就是设置  等价于ele.className = "one"
    // prop("className");       一个参数就是获取  等价于ele.className
    GetEle.prototype.prop = function (key, val) {
        // this -> 调用此方法的实例化对象 (伪数组)
        if (arguments.length >= 2) {
            this.each((ele, index) => {
                ele[key] = val;
            })
            return $(this);  // 返回实例化对象(新的)
        } else if (arguments.length == 1) {
            return this[0][key];
        } else {
            var err = new Error("Place enter at last one parameter!") // 请传入至少一个参数
            throw err;
        }

    }

    GetEle.prototype.addClass = function (clsStr) {  // clsStr => 传入class名的字符串 "one"  "one two three"
        var list = clsStr.split(/\s+/);
        this.each((ele) => {
            ele.classList.add(...list); //多个class名之间用逗号分隔
        })
        return $(this);  // 返回实例化对象(新的)
    }

    GetEle.prototype.removeClass = function (clsStr) {  // clsStr => 传入class名的字符串 "one"  "one  two three"
        var list = clsStr.split(/\s+/);
        this.each((ele) => {
            console.log(ele.classList, list);
            ele.classList.remove(...list); //多个class名之间用逗号分隔
        })
        return $(this);  // 返回实例化对象(新的)
    }


    GetEle.prototype.hasClass = function (className) {  // clsStr => 传入class名的字符串 "one"  "one two three"
        var flag = false;  // 假设没有
        this.each((ele) => {
            if (ele.classList.contains(className)) {  // 有一个元素有 就变为true
                flag = true;
            }
        })
        return flag;
    }


    // $(".list li").css({ backgroundColor: "red" });   // .list li都变红
    // $(".list li").eq(1).css({ backgroundColor: "red" }); // .list li中下标为1的li变红

    // eq(index)  返回对应下标的元素(形成的实例化对象)
    GetEle.prototype.eq = function (index) {
        // this -> 调用此方法的实例化对象 (伪数组)
        // return this[index];   // 此写法返回原生DOM元素 => 不能使用GetEle.prototype对象的方法

        return $(this[index]); // 传入原生dom元素 将其转化为实例化对象 (伪数组), 能使用GetEle.prototype对象的方法
    }

    // $(".list li").eq(1).siblings().css({ backgroundColor: "red" }); // .list li中除下标为1以外的同级li变红

    // siblings(index)  返回除特定元素以外的同级元素(形成的实例化对象)
    GetEle.prototype.siblings = function (index) {
        // this -> 调用此方法的实例化对象 (伪数组)
        // return this[index];   // 此写法返回原生DOM元素 => 不能使用GetEle.prototype对象的方法

        var ele = this[0]; // 默认获取集合中的第一个元素 (取出特定元素)
        var parentEle = ele.parentElement; //找到目标元素的父元素
        var children = parentEle.children; //父元素中所有的子元素 (包含自身)


        var list = Array.from(children).filter(item => item !== ele); // 筛选出除自身以外的同级元素

        return $(list);  // 将其转化为实例化对象
    }







    function $(selector) {  // 调用$()方法 => 快速获取元素,返回元素的集合(实例化对象)
        return new GetEle(selector);
    }


    // GetEle => 参数多样式
    // var liList = document.querySelectorAll(".list li");

    // console.log(liList);
    // console.log($(liList));





    // jquery  =>  write less , do more
    $(".setHtml").click(function () {
        $(".list li").text("123123");
    })

    $(".getHtml").click(function () {
        alert($(".list li").text());
    })

    $(".hide").click(function () {
        $(".list li").hide();
    })

    $(".show").click(function () {
        $(".list li").show();
    })

    $(".setCss").click(function () {

        // css(cssKey,cssVal) =>每次调用方法只能设置一个, 如果需要同时设置多个css属性=>麻烦
        // $(".list li").css("width", "200px");
        // $(".list li").css("height", "200px");
        // $(".list li").css("background-color", "red");

        // 优化: 每次设置css时,css属性名和属性值都以键值对形式存在 => 可以传入一个对象参数
        $(".list li").css({
            width: "200px",
            height: "200px",
            backgroundColor: "red",
        })

    })

    $(".getCss").click(function () {
        alert($(".list li").css("width"));
    })


    $(".setWidth").click(function () {
        console.log(1111);
        // $(".list li").width("200px");
        $(".list li").width(200);
    })

    $(".getWidth").click(function () {
        alert($(".list li").width());
    })

    $(".attr").click(function () {
        // html属性
        $(".list li").attr("class", "one two three");
        $(".list li").attr("title", "hello");
        $(".list li").attr("data-msg", "world");
    })

    $(".prop").click(function () {
        // 元素节点的内置属性  ele.className  ele.clientWidth
        $(".list li").prop("className", "one two three");
        $(".list li").prop("title", "hello");
    })


    $(".addClass").click(function () {
        $(".list li").addClass("one two three four five")
    })

    $(".removeClass").click(function () {
        $(".list li").removeClass("one three five")
    })

    $(".hasClass").click(function () {
        alert($(".list li").hasClass("one"))
    })

    $(".eq").click(function () {
        console.log($(".list li").eq(1));
        $(".list li").eq(1).css({ backgroundColor: "red" });
    })

    $(".siblings").click(function () {
        console.log($(".list li").eq(1));
        console.log($(".list li").eq(1).siblings());
        $(".list li").eq(1).siblings().css({ backgroundColor: "red" });
    })

    $(".chain").click(function () {
        // 链式操作前提: 调用方法后返回值还是相同类型的数据 (原本的/新的)
        // 小技巧: 除取值操作外,其他方法都返回实例化对象,可以继续链式调用 GetEle.prototype上的方法
        $(".list li").eq(1).html("123123").width(200).height(200).css({ backgroundColor: "red" }).siblings().html("asdasd").width(100).height(100).css({ backgroundColor: "blue" });
    })


    $(".list li").click(function () {

        // $(".list li").css("backgroundColor", "transparent");  // 先取出所有li的样式
        // $(this).css("backgroundColor", "red"); // 把当前li变为红色

        // $(this).addClass("active"); // 被点击的li变为活跃状态
        // $(this).siblings().removeClass("active");  // 除被点击li以外的同级元素  清除活跃状态

        $(this).addClass("active").siblings().removeClass("active");
    })
</script>

</html>